﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Configuration;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Wicresoft.ARConnector;
using Wicresoft.Data.EntityFramework;
using Wicresoft.Entity.AREntity;
using Wicresoft.Logger;
using Wicresoft.Service.Tasks;

namespace Wicresoft.Task.EmployeeSync
{
    public class SyncTask:IntervalTask
    {
        private static readonly string ARServer = ConfigurationManager.AppSettings["ARServer"];
        private static readonly string ARUid = ConfigurationManager.AppSettings["ARUid"];
        private static readonly string ARPwd = ConfigurationManager.AppSettings["ARPwd"];

        private ARProxy<People> proxy;
        private int batchCurrent;
        private int batchLimit;
        private bool enableTransaction;


        protected override void Execute()
        {
            DateTime begin = DateTime.Now;
            Log.Info(Name, "Begin to sync employees...");

            batchLimit = int.Parse(Bag.BatchLimit);
            enableTransaction = batchLimit != 0;

            // Log.Info(Name, "BuikTransaction is {0}", enableTransaction ? "enabled" : "disabled");

            using (proxy = new ARProxy<People>(ARServer, ARUid, ARPwd,string.Empty))
            {
                using (ARSysContext dc = new ARSysContext())
                {
                    var csvList = ReadCSV();
                    Traverse(GetDbSource(dc), csvList, Push);
                    csvList = null;
                }
          
            
            }

            DateTime end = DateTime.Now;
            var span = end - begin;

            Log.Info(Name, "End of sync employees... {0} elapsed", span);
        }


        private void Traverse(IEnumerable db, IEnumerable csv, ProceedEmployee act)
        {
            if (db == null || csv == null || act == null)
                throw new ArgumentNullException("db, csv, act");

            var t = db.GetType().GetGenericArguments();
            var k = csv.GetType().GetGenericArguments();

            // Type of csv is comiple generated, so it never equal to the one of db##
            //if (t.Length != 1 || k.Length != 1 || t.First().FullName != k.First().FullName)
            //    throw new ArgumentException("db and csv's generic type must be identical");

            //KeyAttribute
            var key = t.First().GetProperties().FirstOrDefault(p => p.HasAttribute<KeyAttribute>());
            if (key == null)
                throw new ArgumentException("db or csv's generic type must contains at least one property that is decorated with KeyAttribute.");

            // begin of algorithm
            int csvKey = 0;
            int dbKey = 0;

            IEnumerator dbEnumerator = db.GetEnumerator();
            if (dbEnumerator.MoveNext())
                dbKey = int.Parse(key.GetValue(dbEnumerator.Current, null) as string);



            foreach (var csvEntity in csv)
            {
                csvKey = int.Parse(key.GetValue(csvEntity, null) as string);

                if (dbKey == csvKey)
                {
                    // update or delete

                    act(dbEnumerator.Current, csvEntity);
                    if (dbEnumerator.MoveNext())
                        dbKey = int.Parse(key.GetValue(dbEnumerator.Current, null) as string);
                }
                else
                {
                    // create
                    act(null, csvEntity);
                }
            }
            // end of algorithm
        }

        private delegate void ProceedEmployee(object dbEntity, object csvEntity);

        private void Push(object dbEntity, object csvEntity)
        {
            var db = dbEntity as People;
            var csv = csvEntity as People;

            PushInternal(db, csv);
        }

        private bool PushInternal(People db, People csv)
        {
            try
            {
                // login name tolower
                csv.CIL = csv.CIL.ToLower();
                if (csv.ProfileStatus == 5)
                {
                    if (db != null)
                    {
                        // delete
                        csv.PeopleId = db.PeopleId;

                        proxy.UpdateEntry(csv);
                        proxy.RemoveEntry(csv);
                        Log.Info(Name, "DeleteEntry -- ID={0}, Name={1},LoginID={2}", csv.EmployeeID, csv.Name, csv.CIL);
                        return true;
                    }
                }
                else
                {
                    if (db == null)
                    {
                        // create

                        proxy.CreateEntry(csv);
                        Log.Info(Name, "CreateEntry -- ID={0}, Name={1},LoginID={2}", csv.EmployeeID, csv.Name, csv.CIL);
                        return true;
                    }
                    else
                    {
                        if (db != csv)
                        {
                            // update
                            csv.PeopleId = db.PeopleId;
                            proxy.UpdateEntry(csv);
                            Log.Info(Name, "SetEntry -- ID={0}, Name={1},LoginID={2}", csv.EmployeeID, csv.Name, csv.CIL);
                            return true;
                        }
                    }
                }
            }
            catch (Exception e)
            {
                Log.Exception(Name, e);
            }

            return false;
        }

     


        private IEnumerable GetCsvSource(StreamReader sr)
        {
            string pwd = Bag.DefaultPwd;

            int limit = int.Parse(Bag.SyncLimit);
            if (limit <= 0) limit = int.MaxValue;

            // Always skip the first column line!
            sr.ReadLine();

            while (limit-- > 0)
            {
                string buffer = sr.ReadLine();
                if (string.IsNullOrEmpty(buffer))
                    break;

                People emp = null;
                try
                {
                    dynamic extra = new System.Dynamic.ExpandoObject();

                    extra.Password = Bag.DefaultPwd;
                    extra.ClientType = 2000;
                    extra.LicenseType = 2;
                    extra.SupportStaff = 1;
                    extra.Company = "Alcatel-Lucent Shanghai Bell";

                    emp = People.FromRawData(buffer, extra);
                    // to lower CIL account in csv file
                    emp.CIL = emp.CIL.ToLower();
                }
                catch (ArithmeticException ari)
                {
                    // by design, it means the stream reader 
                    //  hits the bottom of the csv file
                    Log.Exception(Name, ari);
                    break; // make sure we break the loop
                }
                catch (Exception e)
                {
                    if (emp != null)
                        Log.Error(Name, "Failed to read employee data {0} [{1}]", emp.EmployeeID, emp.Name);
                    Log.Exception(Name, e);
                }

                if (emp != null)
                    yield return emp;
            }
        }

        private IEnumerable GetDbSource(ARSysContext dc)
        {
            var source = dc.People.SqlQuery(@"
SELECT
    [Accounting_Number] AS [EmployeeID], 
    [Person_ID] AS [PeopleId], 
    LOWER([Remedy_Login_ID]) AS [CIL], 
    [ACD_Phone_Num] AS [UPI], 
    [Nick_Name] AS [Name], 
    [First_Name] AS [FirstName], 
    [Middle_Initial] AS [MidName], 
    [Last_Name] AS [LastName], 
    [Full_Name] AS [FullName], 
    [Support_Staff] AS [SupportStaff], 
    [Profile_Status] AS [ProfileStatus], 
    [Corporate_E_Mail] AS [Email]
FROM [CTM_People]
WHERE [Accounting_Number] IS NOT NULL 
ORDER BY CONVERT(INT, [Accounting_Number])");

            return source;
        }

        private IEnumerable ReadCSV()
        {
            string pwd = Bag.DefaultPwd;

            int limit = int.Parse(Bag.SyncLimit);
            if (limit <= 0)
            {
                limit = int.MaxValue;
            }
            List<People> peopleList = new List<People>();
            using (StreamReader sr = new StreamReader(Bag.EmployeeSource))
            {

                sr.ReadLine();
                while (limit-- > 0)
                {
                    string content = sr.ReadLine();
                    if (string.IsNullOrEmpty(content))
                    {
                        return peopleList;
                    }

                    People emp = null;
                    try
                    {
                        dynamic extra = new System.Dynamic.ExpandoObject();

                        extra.Password = Bag.DefaultPwd;
                        extra.ClientType = 2000;
                        extra.LicenseType = 2;
                        extra.SupportStaff = 1;
                        extra.Company = "Alcatel-Lucent Shanghai Bell";

                        emp = People.FromRawData(content, extra);
                        // to lower CIL account in csv file
                        emp.CIL = emp.CIL.ToLower();
                        if (emp != null)
                        {
                            peopleList.Add(emp);
                        }

                    }
                    catch (ArithmeticException ari)
                    {
                        // by design, it means the stream reader 
                        //  hits the bottom of the csv file
                        Log.Exception(Name, ari);
                        break; // make sure we break the loop
                    }
                    catch (Exception e)
                    {
                        if (emp != null)
                            Log.Error(Name, "Failed to read employee data {0} [{1}]", emp.EmployeeID, emp.Name);
                        Log.Exception(Name, e);
                    }
                }
            }
            return peopleList;
        }
    }
}
